package com.alex.statistics.method.descriptive;

import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Component
public class DistributionAnalyzer {

    /**
     * 计算频率分布
     *
     * @param data 输入数据数组
     * @param bins 区间划分，如 [0, 10, 20, 30] 表示 0-10, 10-20, 20-30 三个区间
     * @return 每个区间的频率统计
     */
    /**
     * 计算数据在各个区间的频率分布
     * - 当bins的最后一个值等于数据最大值时，最后一个区间使用左闭右闭 [bins[n-2], bins[n-1]]
     * - 否则，所有区间都使用左闭右开 [bins[i], bins[i+1})
     * - 超出最大bin值的数值单独统计
     *
     * @param data 要统计的数据数组
     * @param bins 区间分割点数组
     * @return 包含每个区间及其对应频率的Map
     */
    /**
     * 计算数据在各个区间的频率分布
     * - 当bins的最后一个值等于数据最大值时，最后一个区间使用左闭右闭 [bins[n-2], bins[n-1]]
     * - 否则，所有区间都使用左闭右开 [bins[i], bins[i+1})
     * - 超出最大bin值的数值单独统计，若频率为0则不显示该区间
     *
     * @param data 要统计的数据数组
     * @param bins 区间分割点数组
     * @return 包含每个区间及其对应频率的Map
     */
    public Map<String, Integer> frequencyDistribution(double[] data, double[] bins) {
        // 对数据和区间分割点进行排序
        Arrays.sort(data);
        Arrays.sort(bins);

        // 边界条件处理
        if (bins.length < 2) {
            throw new IllegalArgumentException("bins数组至少需要包含2个元素");
        }

        // 获取数据中的最大值
        double dataMax = data.length > 0 ? data[data.length - 1] : Double.NaN;
        // 获取bins中的最大值
        double binsMax = bins[bins.length - 1];

        // 判断最后一个区间是否需要使用闭区间
        boolean lastBinIsClosed = (data.length > 0) && (dataMax == binsMax);

        Map<String, Integer> freqDist = new HashMap<>();

        // 初始化频率分布Map，创建各个区间
        for (int i = 0; i < bins.length - 1; i++) {
            String range = String.format("%.2f-%.2f", bins[i], bins[i + 1]);
            freqDist.put(range, 0);
        }

        // 统计每个区间的频率
        int lastRangeCount = 0; // 单独记录最后范围的计数
        for (double value : data) {
            boolean inRange = false;
            // 检查是否属于各个区间
            for (int i = 0; i < bins.length - 1; i++) {
                // 判断是否为最后一个区间
                if (i == bins.length - 2 && lastBinIsClosed) {
                    // 最后一个区间且需要闭区间：左闭右闭 [bins[i], bins[i+1]]
                    if (value >= bins[i] && value <= bins[i + 1]) {
                        String range = String.format("%.2f-%.2f", bins[i], bins[i + 1]);
                        freqDist.put(range, freqDist.get(range) + 1);
                        inRange = true;
                        break;
                    }
                } else {
                    // 其他情况：左闭右开 [bins[i], bins[i+1})
                    if (value >= bins[i] && value < bins[i + 1]) {
                        String range = String.format("%.2f-%.2f", bins[i], bins[i + 1]);
                        freqDist.put(range, freqDist.get(range) + 1);
                        inRange = true;
                        break;
                    }
                }
            }
            // 如果不属于前面任何区间，且大于最大bin值，则计入最后一个范围
            if (!inRange && value > binsMax) {
                lastRangeCount++;
            }
        }

        // 只有当最后范围有数值时才添加到结果中
        if (lastRangeCount > 0) {
            String lastRange = String.format(">%.2f", binsMax);
            freqDist.put(lastRange, lastRangeCount);
        }

        return freqDist;
    }

    /**
     * 计算平均值
     */
    private double mean(double[] data) {
        double sum = 0.0;
        for (double num : data) {
            sum += num;
        }
        return sum / data.length;
    }

    /**
     * 计算方差
     */
    private double variance(double[] data) {
        double mean = mean(data);
        double temp = 0;
        for (double num : data) {
            temp += (num - mean) * (num - mean);
        }
        return temp / data.length;
    }

    /**
     * 计算偏度 (Skewness)
     * 偏度衡量分布的不对称性
     * 偏度 > 0: 右偏(正偏)，右侧有长尾
     * 偏度 < 0: 左偏(负偏)，左侧有长尾
     * 偏度 = 0: 对称分布
     */
    public double skewness(double[] data) {
        double mean = mean(data);
        double variance = variance(data);
        double stdDev = Math.sqrt(variance);
        double skewness = 0;

        for (double num : data) {
            skewness += Math.pow((num - mean) / stdDev, 3);
        }

        // 样本偏度需要乘以 n/((n-1)(n-2)) 进行调整
        skewness *= ((double) data.length) / ((data.length - 1) * (data.length - 2));

        return skewness;
    }

    /**
     * 计算峰度 (Kurtosis)
     * 峰度衡量分布的尖锐程度和尾部厚度
     * 峰度 > 3: 比正态分布更尖峰厚尾
     * 峰度 < 3: 比正态分布更平峰薄尾
     * 峰度 = 3: 与正态分布相同
     * 注意: 这里计算的是超额峰度(excess kurtosis)，即结果已减去3
     */
    public double kurtosis(double[] data) {
        double mean = mean(data);
        double variance = variance(data);
        double kurtosis = 0;

        for (double num : data) {
            kurtosis += Math.pow((num - mean), 4);
        }

        kurtosis = kurtosis / (data.length * Math.pow(variance, 2));

        // 计算超额峰度(减去3)
        // 样本峰度需要调整
        double n = data.length;
        kurtosis = ((n + 1) * kurtosis - 3 * (n - 1)) * (n - 1) / ((n - 2) * (n - 3));
        kurtosis += 3; // 转换为普通峰度(不减3)

        return kurtosis - 3; // 返回超额峰度
    }


}